<template>
  <div class="icon">
    <template v-for="(item, index) in data.toolbar">
      <!-- 当用户点击按钮 失去焦点之后调用onBlur -->
      <button v-if="item.value === 'emoji'" :key="index" :title="item.title"
        class="emoji p-1 rounded-md hover:bg-gray-200 text-gray-500" @click="item.onClick" @blur="item.onBlur">
        <span class="text-[30px] flex items-center justify-center">
          <!-- 动态渲染组件 :is是渲染绑定 他告诉vue应该渲染哪个组件  -->
          <!-- 如果item.icon存在 而且它的类型不等于string的话 就渲染item.icon这个数组 -->
          <component v-if="item.icon && typeof item.icon !== 'string'" :is="item.icon" />
          <img v-else :src="item.icon" :alt="item.title" class="w-auto h-[1em]" />
          <!-- 当item.icon不存在时  img中src的值为item.icon  alt中的值为item.title -->
        </span>
      </button>
    </template>
    <!-- 输入框 -->
    <!-- 这里的ref=editor是dom的引用名称 -->
    <div ref="editor" :class="['empty:after:content-[attr(placeholder)]', {
      'empty:after:text-gray-400': !disabled,
      'empty:after:text-gray-300': disabled,
    }]" contenteditable class="shuru" :placeholder="placeholder" @keydown="event.handleKeydown"
      @input="event.handleInput" />
    <!-- @input输入框输入事件  @keydown按下键盘事件 -->
    <template v-for=" (item, index) in data.toolbar">
      <button v-if="item.value !== 'emoji'" :key="index" :title="item.title"
        class="p-1 rounded-md hover:bg-gray-200 text-gray-500" @click="item.onClick" @blur="item.onBlur">
        <span class="text-[30px] flex items-center justify-center">
          <component v-if="item.icon && typeof item.icon != 'string'" :is="item.icon" />
          <img v-else :src="item.icon" :alt="item.title" class="w-auto h-[1em]">
        </span>
      </button>
    </template>

    <!-- 弹出层 -->
    <div v-show="data.showModal" ref="modal" class="showClass" :class="data.modalClass" :style="{
      top: `${data.modalPosition.top}px`,
      left: `${data.modalPosition.left}px`,
    }" />
    <!-- 点击任意位置都可以收回弹出层 -->
    <div v-if="data.showModal" class="z-40 fixed inset-0 m-auto" @click="data.showModal = false"></div>
  </div>
  <!-- 取消在iphone端的点击输入框页面变大效果 -->
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="viewport"
    content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
</template>

<script setup lang="ts">
import './style.css'
import EmojiIcon from '@/components/icon/Emotion.vue';
import JargonIcon from '../icon/Jaygon.vue';
import CardIcon from '../icon/Car.vue';
import { type IProps } from './props';
import { type IEmits } from './emits';
import { nextTick, onMounted, reactive, ref, shallowRef, watch } from 'vue';
import { Confirm } from '../modal';
import { emojiToText, htmlToText, textToEmoji } from '@/utils/render';
import { EMOJI } from '../icon';

const props = withDefaults(defineProps<IProps>(), {
  placeholder: '按Enter发送',
  showToolBarTitle: false,
  disabled: false,
});
const emits = defineEmits<IEmits>();

const model = defineModel({ type: String });
// 这里调用的是输入事件
const editor = ref<HTMLDivElement>();
const modal = ref<HTMLDivElement>();
const scrollbar = ref();

onMounted(() => {
  const emoji = document.querySelector('.emoji') as HTMLButtonElement
  const nana = document.querySelector('.nana') as HTMLDivElement
  const shuru = document.querySelector('.shuru') as HTMLDivElement
  const messageBao = document.querySelector('.messageBao') as HTMLDivElement
  const chatPage = document.querySelector('.chatPage') as HTMLDivElement

  emoji.addEventListener('click', () => {
    nana.style.height = '45%'
    messageBao.style.height = '55%'
  })
  shuru.addEventListener('click', () => {
    nana.style.height = '10%'
    messageBao.style.height = '90%'
    chatPage.scrollTop = chatPage.scrollHeight
  })
})

const data = reactive({
  toolbar: [
    {
      value: 'emoji',
      // 浅层的响应式引用
      icon: shallowRef(EmojiIcon),
      title: '表情',
      // 点击方法的参数是鼠标事件
      onClick: (e: MouseEvent) => {
        if (data.currentToolBar !== 'emoji') {
          data.showModal = false;
          data.showModal = true;
        } else {
          data.showModal = !data.showModal;
        }
        data.currentToolBar = 'emoji';

        if (modal.value) {
          modal.value.innerHTML = '';
        }
        // 鼠标事件当时正在处理该事件的元素 的类型是 HTMLDivElement
        const el = e.currentTarget as HTMLDivElement;
        // 返回元素的大小及其相对于视口的位置 将值赋rect
        const rect = el.getBoundingClientRect();

        // 创建表情弹窗
        modal.value?.setAttribute(
          'class',
          `${data.modalClass} grid grid-cols-8 gap-2`
        );
        data.modalPosition.top = rect.top - 280 - 16;
        data.modalPosition.left = rect.left;

        const fragment = new DocumentFragment();
        data.emoji.forEach(img => {
          fragment.appendChild(img);
        });
        modal.value?.appendChild(fragment);
      },
      // 
      onBlur: () => {
        if (data.currentToolBar !== 'emoji') {
          data.showModal = false;
        }
      },
    },
    {
      value: 'jargon',
      icon: shallowRef(JargonIcon),
      title: '话术',
      onClick: (e: MouseEvent) => {
        // 当前点击的工具栏为话术时
        if (data.currentToolBar !== 'jargon') {
          data.showModal = false;
          data.showModal = true;
        } else {
          data.showModal = !data.showModal;
        }
        data.currentToolBar = 'jargon';

        if (modal.value) {
          modal.value.innerHTML = '';
        }

        const el = e.currentTarget as HTMLDivElement;
        const rect = el.getBoundingClientRect();

        modal.value?.setAttribute(
          'class',
          `${data.modalClass} w-[380px] flex flex-col-reverse gap-1`
        );
        data.modalPosition.top = rect.top - 280 - 16;
        data.modalPosition.left = rect.left - 280;

        const fragment = new DocumentFragment();
        data.jargon.forEach(span => {
          fragment.appendChild(span);
        });
        modal.value?.appendChild(fragment);
      },
      // 失去焦点时
      onBlur: () => {
        if (data.currentToolBar !== 'jargon') {
          data.showModal = false;
        }
      },
    },
    {
      value: 'card',
      icon: shallowRef(CardIcon),
      title: '卡片',
      onClick: async (_: MouseEvent) => {
        // 如果editor存在
        if (editor.value) {
          // 这里的Confirm确认框的方法
          const ok = await Confirm({
            // 内容:
            message: '确定发送卡片吗？',
          });
          if (ok) {
            emits('enter', '【卡片消息】');
          }
        }
      },
    },
  ],
  /**
   * 拟态类
   */
  modalClass:
    'modal fixed z-50 p-2  bg-white h-[280px] overflow-x-hidden overflow-y-auto rounded',
  /**
   * 显示模式
   */
  showModal: false,
  /**
   * 当前工具栏 */
  currentToolBar: '',
  // 类型是对象，有两个键值对，其一键名是top，其二键名是left，其值都为0
  modalPosition: {
    top: 0,
    left: 0,
  },
  // 键名是emoji，类型是一个数组，数组里面是HTMLImageElement的类型，就是这个数组只能包含图片元素
  emoji: <HTMLImageElement[]>[],
  // 键名是jargon，类型是一个数组，数组里面包括着HTMLDiveElement类型 ，就是html<div>中的DOM元素的数组类型
  jargon: <HTMLDivElement[]>[],
});

const event = {
  // handleInput方法参数的类型是输入事件
  handleInput(e: InputEvent) {
    // e是一个事件对象,它代表了一个输入事件,当用户输入元素如<input>,<div>交互时,会触发输入事件
    const el = e.target as HTMLElement;
    // 获取触发事件的元素，调用trim()方法用于移除字符串两端的空白字符
    const message = el.innerHTML.trim();
    //  htmlToText()将转换后的消息从HTML格式转换为纯文本格式
    // emojiToText将消息中的emoji转换为文本
    model.value = htmlToText(emojiToText(message));
    if (scrollbar.value) {
      scrollbar.value.scrollToBottom();
    }
  },
  // 按下 键盘事件
  handleKeydown(e: KeyboardEvent) {
    // el是触发事件的DOM元素
    const el = e.target as HTMLElement;
    if (e.key === 'Enter') {
      // 取消浏览器的默认事件
      e.preventDefault();
      // el.innerHTML用于设置或获取el元素内部HTML内容 trim()用于删除前后空白符和行结束符
      const message = el.innerHTML.trim();
      if (message) {
        emits('enter', htmlToText(emojiToText(message)));

        model.value = '';
        el.innerHTML = '';
      }
      return;
    }
    if (e.key === 'Tab') {
      e.preventDefault();
      this.insert('\t');
      return;
    }
  },
  reset() {
    nextTick(() => {
      if (editor.value) {
        editor.value.innerHTML = '';
      }
    });
  },
  insert(content: string) {
    // window.getSelection()返回一个Selection对象，该对象包含当前在文档中被用户或脚本选定的文本范围
    const selection = window.getSelection() as Selection;
    // 返回选区中'Range'对象的数量
    if (selection.rangeCount > 0) {
      // 返回选区中指定索引的'Range'对象
      const range = selection.getRangeAt(0);
      // createTextNode是一个DOM方法，
      const tabNode = document.createTextNode(content);
      range.insertNode(tabNode);
      range.setStartAfter(tabNode);
      range.setEndAfter(tabNode);
      selection.removeAllRanges();
      selection.addRange(range);
    }
    if (scrollbar.value) {
      scrollbar.value.scrollToBottom();
    }
  },
  append(content: string) {
    nextTick(() => {
      if (editor.value) {
        editor.value.innerHTML += content;
        data.showModal = false;
      }
    });
  },
};

nextTick(() => {
  const emojiNames = Object.keys(EMOJI);
  emojiNames.forEach((name: keyof typeof EMOJI) => {
    const img = document.createElement('img');
    img.src = EMOJI[name];
    img.style.width = '34px';
    img.style.height = '34px';
    img.setAttribute(
      'class',
      'cursor-pointer hover:bg-gray-100 p-2 rounded'
    );
    img.addEventListener('click', () => {
      event.append(textToEmoji(name));
    });
    data.emoji.push(img);
  });
  // 粘贴事件监听
  if (editor.value) {
    editor.value?.addEventListener('paste', function (e) {
      // 阻止默认的粘贴行为
      e.preventDefault();

      // 获取剪贴板数据
      // @ts-ignore
      var clipboardData = e.clipboardData || window.clipboardData;
      var pastedText = clipboardData.getData('text/plain');

      // 创建一个新的文本节点并插入到光标位置
      var range = window.getSelection()?.getRangeAt(0);
      var textNode = document.createTextNode(htmlToText(pastedText));
      range?.deleteContents();
      range?.insertNode(textNode);

      range?.setStartAfter(textNode);
      range?.setEndAfter(textNode);
      window.getSelection()?.removeAllRanges();
      window.getSelection()?.addRange(range!);
    });
  }
  // 监听输入框发生变化
  if (editor.value) {
    const observer = new MutationObserver(mutations => {
      mutations.forEach(mutation => {
        // const el = mutation.target as HTMLElement;
        model.value = htmlToText(
          emojiToText(editor.value?.innerHTML || '')
        );
      });
    });
    observer.observe(editor.value, {
      childList: true,
      subtree: true,
      characterData: true,
    });
  }
});

watch(
  () => props.jargon,
  () => {
    if (
      props.jargon &&
      Object.keys(props.jargon).length > 0 &&
      data.jargon.length === 0
    ) {
      const tabs = document.createElement('div');
      tabs.setAttribute(
        'class',
        'flex flex-row gap-1 border-t border-gray-200 p-1'
      );
      const content = document.createElement('div');
      content.setAttribute(
        'class',
        'flex-1 flex flex-col items-start gap-2'
      );

      const keys = Object.keys(props.jargon);
      let current = '';
      keys.forEach((key, index) => {
        const button = document.createElement('button');
        button.innerHTML = key;
        const btnClass = 'px-2 py-1 rounded hover:bg-orange-100';
        button.setAttribute('class', btnClass);
        button.addEventListener('click', () => {
          if (current === key) {
            return;
          }

          current = key;

          // 移除其他按钮样式
          const btns = tabs.childNodes;
          btns.forEach(btn => {
            if (btn.nodeType === 1) {
              (btn as HTMLButtonElement).setAttribute(
                'class',
                btnClass
              );
            }
          });

          // 当前按钮样式
          button.setAttribute(
            'class',
            `${btnClass} bg-orange-100`
          );

          content.innerHTML = '';

          props.jargon![key].forEach(text => {
            const span = document.createElement('span');
            span.innerHTML = text;
            span.setAttribute(
              'class',
              'cursor-pointer hover:bg-gray-100 p-2 rounded bg-gray-50'
            );
            span.addEventListener('click', () => {
              event.append(text);
            });
            content.appendChild(span);
          });
        });
        if (index === 0) {
          button.click();
        }
        tabs.appendChild(button);
      });
      data.jargon.push(tabs, content);
    }
  },
  { deep: true }
);

defineExpose({
  reset: event.reset,
});

</script>

<style></style>